#include "GamePadInput.h"
#include <mutex>
#include <windows.h>
#include <dinput.h>

#include <QDebug>




static QString DebugErrOut(const char* errTag,HRESULT res){

  QString err(errTag);
  if(res!=0){
    err+=QString::number((unsigned long)res,16);
  }
  qDebug().noquote()<<err<<Qt::endl;

  return err;
}

static QString PrintfGUID(GUID& guid){
  QString r("GUID:");

  r+=QString::number(guid.Data1,16)+"-";
  r+=QString::number(guid.Data2,16)+"-";
  r+=QString::number(guid.Data3,16)+"-";
  r+=QString::number(guid.Data4[0],16);
  r+=QString::number(guid.Data4[1],16);
  r+=QString::number(guid.Data4[2],16);
  r+=QString::number(guid.Data4[3],16);
  r+=QString::number(guid.Data4[4],16);
  r+=QString::number(guid.Data4[5],16);
  r+=QString::number(guid.Data4[6],16);
  r+=QString::number(guid.Data4[7],16);

  qDebug()<<r<<Qt::endl;

  return r;
}




typedef struct{
  LPDIRECTINPUTDEVICE8 DirectInputDevice8;
  GamePadInput::JoyPadStatus Status;
}GamePadInput_Device;

typedef struct{
  HWND winHandle;

  LPDIRECTINPUT8 Directinput8;

  GamePadInput_Device* Devices;
  int DevciesMaxNum;
  int ValidNum;

  
  std::mutex* StatusLock;
  HANDLE Thread;
  HANDLE StopEvent;

}GamePadInput_Handle;




static HRESULT ReadState(GamePadInput_Handle* handle,GamePadInput_Device* dHandle){
  DIJOYSTATE JoyState;
  HRESULT res;
  

  res=dHandle->DirectInputDevice8->Poll();
  if((res!=DI_OK)&&(res!=DI_NOEFFECT)){
    dHandle->DirectInputDevice8->Acquire();
    Sleep(100);
    return res;
  }

  res=dHandle->DirectInputDevice8->GetDeviceState(sizeof(DIJOYSTATE),(LPVOID)&JoyState);
  if(res!=DI_OK){
    Sleep(100);
    return res;
  }

  uint32_t Buttons=0U;
  for(int i=0;i<32;i++){
    Buttons|=((uint32_t)((JoyState.rgbButtons[i]>>7)&0x01U))<<i;
  }

  handle->StatusLock->lock();

  dHandle->Status.Buttons=Buttons;
  dHandle->Status.X=(int)JoyState.lX;
  dHandle->Status.Y=(int)JoyState.lY;
  dHandle->Status.Z=(int)JoyState.lZ;
  dHandle->Status.RX=(int)JoyState.lRx;
  dHandle->Status.RY=(int)JoyState.lRy;
  dHandle->Status.RZ=(int)JoyState.lRz;

  handle->StatusLock->unlock();

  return res;
}

static DWORD CALLBACK GamePad_Thread(LPVOID v){
  GamePadInput_Handle* handle;
  int i;

  handle=(GamePadInput_Handle*)v;


  for(i=0;i<handle->ValidNum;i++){
    handle->Devices[i].DirectInputDevice8->Acquire();
  }

  for(;;){
    if(WAIT_OBJECT_0==WaitForSingleObject(handle->StopEvent,0)){
      for(i=0;i<handle->ValidNum;i++){
        handle->Devices[i].DirectInputDevice8->Unacquire();
      }
      break;
    }

    for(i=0;i<handle->ValidNum;i++){
      ReadState(handle,&handle->Devices[i]);
    }

    


    Sleep(8);
  }

  return 0U;
}

static HRESULT SetRange(LPDIRECTINPUTDEVICE8 device,DWORD dwObj,DIPROPRANGE* range){
  range->diph.dwSize=sizeof(DIPROPRANGE);
  range->diph.dwHeaderSize=sizeof(DIPROPHEADER);
  range->diph.dwObj=dwObj;
  range->diph.dwHow=DIPH_BYOFFSET;
  range->lMin=-1024;
  range->lMax=1024;
  return device->SetProperty(DIPROP_RANGE,&range->diph);
}

static HRESULT SetDeadBand(LPDIRECTINPUTDEVICE8 device,DWORD dwObj,DIPROPDWORD* range){
  range->diph.dwSize=sizeof(DIPROPDWORD);
  range->diph.dwHeaderSize=sizeof(DIPROPHEADER);
  range->diph.dwObj=dwObj;
  range->diph.dwHow=DIPH_BYOFFSET;
  range->dwData=670;
  return device->SetProperty(DIPROP_DEADZONE,&range->diph);
}

static BOOL CALLBACK EnumCallback(const LPCDIDEVICEINSTANCE DeviceInstance,LPVOID v){
  GamePadInput_Handle* handle;
  GamePadInput_Device* dHandle;
  GUID guidInstance;
  HRESULT res;

  handle=(GamePadInput_Handle*)v;

  guidInstance=DeviceInstance->guidInstance;

  PrintfGUID(guidInstance);

  dHandle=&handle->Devices[handle->ValidNum];
  res=handle->Directinput8->CreateDevice(guidInstance,&dHandle->DirectInputDevice8,nullptr);
  if(res!=DI_OK){
    DebugErrOut("CreateDevice()错误",res);
    return DIENUM_CONTINUE;
  }
  
  res=dHandle->DirectInputDevice8->SetDataFormat(&c_dfDIJoystick);
  if(res!=DI_OK){
    dHandle->DirectInputDevice8->Release();
    DebugErrOut("SetDataFormat()错误",res);
    return DIENUM_CONTINUE;
  }

  // res=dHandle->DirectInputDevice8->SetCooperativeLevel(handle->winHandle,DISCL_FOREGROUND|DISCL_NONEXCLUSIVE);
  res=dHandle->DirectInputDevice8->SetCooperativeLevel(handle->winHandle,DISCL_BACKGROUND|DISCL_NONEXCLUSIVE);
  if(res!=DI_OK){
    dHandle->DirectInputDevice8->Release();
    DebugErrOut("SetCooperativeLevel()错误",res);
    return DIENUM_CONTINUE;
  }


  DIPROPRANGE range;
  DIPROPDWORD value;
  
  SetRange(dHandle->DirectInputDevice8,DIJOFS_X,&range);
  SetRange(dHandle->DirectInputDevice8,DIJOFS_Y,&range);
  SetRange(dHandle->DirectInputDevice8,DIJOFS_Z,&range);
  SetRange(dHandle->DirectInputDevice8,DIJOFS_RX,&range);
  SetRange(dHandle->DirectInputDevice8,DIJOFS_RY,&range);
  SetRange(dHandle->DirectInputDevice8,DIJOFS_RZ,&range);
  SetDeadBand(dHandle->DirectInputDevice8,DIJOFS_X,&value);
  SetDeadBand(dHandle->DirectInputDevice8,DIJOFS_Y,&value);
  SetDeadBand(dHandle->DirectInputDevice8,DIJOFS_Z,&value);
  SetDeadBand(dHandle->DirectInputDevice8,DIJOFS_RX,&value);
  SetDeadBand(dHandle->DirectInputDevice8,DIJOFS_RY,&value);
  SetDeadBand(dHandle->DirectInputDevice8,DIJOFS_RZ,&value);
  



  handle->ValidNum++;
  if(handle->ValidNum>=handle->DevciesMaxNum){
    return DIENUM_STOP;
  }

  return DIENUM_CONTINUE;
}




GamePadInput::GamePadInput(void* winHandle,int MaxNum){

  DeviceHandle=nullptr;

  this->winHandle=winHandle;
  this->MaxNum=MaxNum;

}

GamePadInput::~GamePadInput(){

  if(DeviceHandle!=nullptr){
    Close();
  }

}

void GamePadInput::Open(){
  GamePadInput_Handle* DeviceHandle;
  HRESULT res;

  DeviceHandle=nullptr;

  try{

    DeviceHandle=new GamePadInput_Handle;

    DeviceHandle->Directinput8=nullptr;
    DeviceHandle->winHandle=(HWND)winHandle;
    DeviceHandle->DevciesMaxNum=MaxNum;
    DeviceHandle->ValidNum=0;

    DeviceHandle->Devices=new GamePadInput_Device[MaxNum];

    for(int i=0;i<MaxNum;i++){
      DeviceHandle->Devices[i].DirectInputDevice8=nullptr;

      DeviceHandle->Devices[i].Status.Buttons=0U;
      DeviceHandle->Devices[i].Status.X=0;
      DeviceHandle->Devices[i].Status.Y=0;
      DeviceHandle->Devices[i].Status.Z=0;
      DeviceHandle->Devices[i].Status.RX=0;
      DeviceHandle->Devices[i].Status.RY=0;
      DeviceHandle->Devices[i].Status.RZ=0;

    }

    res=DirectInput8Create(GetModuleHandle(NULL),DIRECTINPUT_VERSION,IID_IDirectInput8,(void**)&DeviceHandle->Directinput8,NULL);
    if(res!=DI_OK){
      DebugErrOut("DirectInput8Create()错误",res);
      throw "DirectInput8Create()错误";
    }

    DeviceHandle->Directinput8->EnumDevices(DI8DEVCLASS_GAMECTRL,EnumCallback,DeviceHandle,DIEDFL_ATTACHEDONLY);
    
    if(DeviceHandle->ValidNum==0){
      throw "无可用的手柄";
    }
    
    DeviceHandle->StatusLock=new std::mutex();
    DeviceHandle->StopEvent=CreateEvent(nullptr,false,false,nullptr);
    DeviceHandle->Thread=CreateThread(nullptr,0,GamePad_Thread,DeviceHandle,0,nullptr);

    this->DeviceHandle=DeviceHandle;

  }catch(const char* e){

    if(DeviceHandle!=nullptr){
      delete[] DeviceHandle->Devices;
      if(DeviceHandle->Directinput8!=nullptr){
        DeviceHandle->Directinput8->Release();
      }
      delete DeviceHandle;
    }

    throw e;
  }

}

void GamePadInput::Close(){
  GamePadInput_Handle* DeviceHandle;

  DeviceHandle=(GamePadInput_Handle*)this->DeviceHandle;
  if(DeviceHandle!=nullptr){
    
    SetEvent(DeviceHandle->StopEvent);
    WaitForSingleObject(DeviceHandle->Thread,INFINITE);

    CloseHandle(DeviceHandle->Thread);
    CloseHandle(DeviceHandle->StopEvent);

    delete DeviceHandle->StatusLock;

    for(int i=0;i<DeviceHandle->ValidNum;i++){
      DeviceHandle->Devices[i].DirectInputDevice8->Release();
    }

    delete[] DeviceHandle->Devices;

    DeviceHandle->Directinput8->Release();

    delete DeviceHandle;
    this->DeviceHandle=nullptr;
  }
  
}

void GamePadInput::GetPadStatus(int PadIndex,JoyPadStatus* Status){
  GamePadInput_Handle* DeviceHandle;

  DeviceHandle=(GamePadInput_Handle*)this->DeviceHandle;
  if(DeviceHandle!=nullptr){
    
    if(PadIndex<DeviceHandle->ValidNum){

      DeviceHandle->StatusLock->lock();
      Status[0]=DeviceHandle->Devices[PadIndex].Status;
      DeviceHandle->StatusLock->unlock();

      return;
    }
  }

  Status->Buttons=0U;
  Status->X=0;
  Status->Y=0;
  Status->Z=0;
  Status->RX=0;
  Status->RY=0;
  Status->RZ=0;
}



